package client.recaudadora;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;   
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.Random;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.x500.X500Principal;
import javax.security.cert.CertificateException;
import javax.security.cert.X509Certificate;

//import org.bouncycastle.asn1.ocsp.Signature;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
 * 
 * @author Mauricio Pradilla Hosie m.pradilla323@uniandes.edu.co
 * @author Martin Uribe Gutierrez m.uribe248@uniandes.edu.co 
 * Infraestructura Computacional
 * Universidad de los Andes
 * Referencias de Algoritmos: 
 * http://www.java2s.com/Code/Java/Security/CatalogSecurity.htm
 * http://www.bouncycastle.org
 * http://www.java2s.com/Code/Java/Network-Protocol/TransferafileviaSocket.htm 
 * http://www.java2s.com/Code/Java/Security/GenerateaDSAsignature.htm 
 * http://oreilly.com/catalog/javacrypt/chapter/ch06.html
 * http://www.java2s.com/Code/Java/Security/SignatureSignAndVerify.htm 
 * http://java.sun.com/developer/technicalArticles/Security/secureinternet/
 * http://www.java2s.com/Code/Java/Security/SignatureSignAndVerify.htm
 * http://www.vbforums.com/showthread.php?t=452739 
 * http://www.java2s.com/Code/Java/Security/SignatureSignAndVerify.htm 
 * 
 * Clase que representa una empresa recaudadora, que contiene nuevos pagos de empleados de m�ltiples empresas, y que deben ser actualizados
 * en batch al servidor de la empresa de pensiones. Todo este proceso viene demarcado por un estricto protocolo y medidas de seguridad, como
 * firmas digitales, certificados digitales, entidad certificadora, llaves asim�tricas, llaves sim�tricas y digests
 * 
 */
public class Recaudadora extends Thread
{
	public static final int DELAY = 50;
	public static final int QUERY_QUANTITY = 1;
	/**
	 * Puerto Seguro por el cual se va a comunicarse con el servidor
	 */
	public   int secPort = 13005;

	/**
	 * Puerto INSeguro por el cual se va a comunicarse con el servidor
	 */
	public   int notSecPort = 11095;

	/**
	 * Puerto por el cual se va a comunicarse con el servidor de la entidad certificadora
	 */
	public   int certPort = 12022; 

	/**
	 * Separador principal para los mensajes entre Cliente - Servidor
	 */
	private static final String SEPARADOR = ":";

	/**
	 * Constante para indicar la direcci�n del servidor
	 */
	private final static String HOST = "192.168.1.4";

	/**
	 * Constante para indicar la direcci�n del servidor
	 */
	private final static String HOST2 = "192.168.1.4";
	
	/**
	 * Constante para indicar el estado del proceso de guardado de archivos en batch
	 */
	private final static String STATUS = "STATUS";

	/**
	 * Constante que indica el inicio de la fase 1
	 */
	private static final String HELLO = "HELLO";

	/**
	 * Constante que indica si una tarea fue exitosa
	 */
	private static final Object OK = "OK";

	/**
	 * Constante que indica la recepci�n del archivo
	 */
	public static final String RECEIVED = "RECEIVED";

	/**
	 * Constante para el separador secundario de los mensajes
	 */
	public static final String SEPARADOR2 = ";";

	/**
	 * Constante para el tipo de caracter
	 */
	public static final String COD = "US-ASCII";	

	/**
	 * Constante para el tama�o de la llave sim�trica
	 */	
	public static final int LLAVE_TAM_BITS = 128;

	/**
	 * Constante que indica la solicitud del servidor para que el cliente se autentique
	 */
	private final static String REQUEST_AUTH = "REQUEST_AUTHENTICATION";

	/**
	 * Formato de encripci�n sim�trico
	 */
	public static final String SYMMETRIC = "DESede";

	/**
	 * Formato de encripci�n asim�trico
	 */
	public static final String ASYMMETRIC = "RSA/ECB/NoPadding";

	/**
	 * Constante que indica en el protocolo que el cliente se va a autenticar
	 */
	public static final String CLIENT_AUTHENTICATION = "CLIENT_AUTHENTICATION";

	/**
	 * Constante del protocolo de comunicaci�n para solicitar el env�o de un archivo de pagos espec�fico
	 */
	private final static String RETRIEVE = "RETRIEVE";

	/**
	 * Constante que se utiliza para los estados en la fase 3 del protocolo
	 */
	public static final String UPDATE = "UPDATE";

	/**
	 * Constante del protocolo de comunicaci�n para informar un error en la autenticaci�n del cliente.
	 */
	private final static String DENIED = "DENIED";

	/**
	 * Socket encargado de manejar la comunicaci�n cliente-servidor
	 */
	private Socket socket;

	/**
	 * Instancia con la que se lee el texto enviado por el servidor como respuesta a una solicitud (character-input stream)
	 */
	private BufferedReader in;

	/**
	 * Instancia donde se escribe la informaci�n a ser enviada al servidor (solicitudes del cliente) (text-output stream)
	 */
	private PrintWriter out;

	/**
	 * Indica si entr� al m�todo para verificar el certificado del servidor
	 */
	private boolean entroCertificado;

	/**
	 * El certificado X.509 para el env�o de la llave p�blica e identificaci�n del cliente
	 */
	private java.security.cert.X509Certificate cert;

	/**
	 * Las llave asim�trica del cliente
	 */
	private static KeyPair pair;

	/**
	 * La llave p�blica del servidor
	 */
	private static PublicKey llaveServidor;

	/**
	 * Representa el proveedor (BouncyCastle)
	 */
	private static Provider proveedor;

	/**
	 * Constante para el n�mero del grupo del caso
	 */
	private String grupo;

	/**
	 * Representa la clase generador, encargada de generar el archivo a enviarle al servidor
	 */
	private Generador generator;

	/**
	 * Opci�n del prop�sito para la conexi�n. Opci�n 1: Actualizar informaci�n. Opci�n 2: Consultar documento
	 */
	private int opcion = 0;

	/**
	 * El nombre del archivo a consultar, dada por el usuario en consola
	 */
	private int nombreConsulta = 0;

	/**
	 * El m�ximo n�mero de grupos del curso
	 */
	public final static int MAX_NUM_GRUPOS = 65535;

	/**
	 * Representa la clase encargada de conectarse al servidor de recaudos y de intercambiar mensajes con este
	 */
	public Recaudadora( )
	{			
		proveedor = new org.bouncycastle.jce.provider.BouncyCastleProvider();
		//Socket generic = new Socket();
		Random generator = new Random();
		// crear Socket para realizar la conexi�n con el servidor
		certPort = /*generator.nextInt(100)+*/10001;
		secPort = /*generator.nextInt(100)+*/20001;
		notSecPort = /*generator.nextInt(100)+*/30001;
	}	
	
	/**
	 * M�todo necesario para correr el hilo que desarrolla una de las transacciones
	 */
	public void run() {

		try {
			Thread.sleep(DELAY);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		try 
		{
			BufferedReader br = new BufferedReader(new InputStreamReader(System.in) );
			System.out.println("----Bienvenido al sistema de Recaudos----\nDigite '1' si desea enviar un documento.\nDigite '2' si desea consultar un documento");
			opcion  = Integer.parseInt(br.readLine( ));
			//System.out.println("Digite el n�mero de grupo del archivo que desea enviar/consultar");
			nombreConsulta = secPort;//Integer.parseInt(br.readLine( ));
			if (!(opcion==1 || opcion==2) && !(0<=nombreConsulta && nombreConsulta<=MAX_NUM_GRUPOS))
			{
				System.out.println("Opci�n No v�lida:"+opcion);
			}
			else
			{
				//proveedor = new org.bouncycastle.jce.provider.BouncyCastleProvider();
				Security.addProvider(proveedor);
				entroCertificado = false;
				generator = new Generador(secPort );
				boolean recibioLlaves = consultarEntidadCertificadora( );
				grupo = Integer.toString(nombreConsulta);
				if (recibioLlaves)
				{
					socket = new Socket (HOST2, secPort);			
					out = new PrintWriter( socket.getOutputStream( ), true );
					in = new BufferedReader( new InputStreamReader( socket.getInputStream( ) ) );		
					// Primer Saludo con el servidor
					out.println( HELLO+SEPARADOR+grupo); 	
					System.out.println("Cliente: HELLO");
					String resp = in.readLine( );			
					System.out.println("\n Servidor: "+resp);											
					procesar(resp);		
				}
				else
				{
					System.out.println("No se recibieron las llaves de la entidad certificadora correctamente");
				}
			}
		}
		catch (SocketException e) 
		{	
			System.out.println("Conexi�n cerrada!");
			e.printStackTrace();
		}
		catch (UnknownHostException e) 
		{			
			e.printStackTrace();
		} 
		catch (IOException e)
		{			
			e.printStackTrace();
		}	
	}

	/**
	 * El cliente se conecta con la entidad certificadora para obtener la llave asim�trica y la llave p�blica del servidor
	 */
	private boolean consultarEntidadCertificadora( ) 
	{
		try 
		{
			socket = new Socket (HOST2, certPort);		
			out = new PrintWriter( socket.getOutputStream( ), true );
			in = new BufferedReader( new InputStreamReader( socket.getInputStream( ) ) );
			out.println(HELLO);
			String llaves = in.readLine( ); // Contiene las llaves codificadas para su posterior uso
			String[] partes = llaves.split(SEPARADOR); 
			if( !partes[0].equals(RECEIVED) && !partes[1].equals(OK) )
			{
				return false;
				//"No se pudo conectar con la entidad certificadora"			
			}
			else
			{
				byte[] aLlaveServidor = destransformar(partes[2].split(SEPARADOR2),"LLAVECIFRADA"); // Obtener la llave p�blica del servidor
				byte[] aLlavePrivCliente = destransformar(partes[3].split(SEPARADOR2),"LLAVECIFRADA"); // Obtener la llave privada del cliente 
				byte[] aLlavePubCliente = destransformar(partes[4].split(SEPARADOR2),"LLAVECIFRADA"); // Obtener la llave p�blica del cliente
				//Crea entidad kf utilizando el algoritmo RSA con el proveedor Bouncycastle
				KeyFactory kf = KeyFactory.getInstance("RSA", "BC");
				//for private keys use PKCS8EncodedKeySpec; for public keys use X509EncodedKeySpec
				PKCS8EncodedKeySpec ks1 = new PKCS8EncodedKeySpec(aLlavePrivCliente); // Se instancia as� para obtener la llave privada
				PrivateKey llavePrivCliente = kf.generatePrivate(ks1); // Se genera la llave privada como una PrivateKey
				X509EncodedKeySpec ks2 = new X509EncodedKeySpec(aLlavePubCliente); // Se instancia as� para obtener la llave p�blica
				PublicKey llavePubCliente = kf.generatePublic(ks2); // Se genera la llave p�blica como una PublicKey
				pair = new KeyPair(llavePubCliente, llavePrivCliente); // Se actualiza la variable pair para utilizar posteriormente
				X509EncodedKeySpec ks3 = new X509EncodedKeySpec(aLlaveServidor);
				llaveServidor = kf.generatePublic(ks3);
				return true;
			}
		}
		catch (IOException e)
		{
			System.out.println(e.getMessage( )); 
		}
		catch (NoSuchProviderException e) 
		{
			System.out.println(e.getMessage( ));
		}
		catch (NoSuchAlgorithmException e)
		{
			System.out.println(e.getMessage( ));
		}
		catch (InvalidKeySpecException e)
		{
			System.out.println(e.getMessage( ));
		}
		catch (Exception e)
		{
			System.out.println(e.getMessage( ));
		}		
		return false;
	}

	/**
	 * M�todo que procesa los mensajes provenientes del servidor e inicia los procesos correspondientes
	 * @param entrada cadena de texto que proviene del socket.
	 * @throws IOException 
	 */
	private void procesar(String entrada) throws IOException
	{
		String[] act= entrada.split(SEPARADOR);
		System.out.println("\n Servidor:"+entrada);		
		if(act[0].equals(STATUS) && act[1].equals(OK))
		{
			if (!entroCertificado && procesarCertificadoServidor())
			{
				String mensaje = in.readLine( );
				System.out.println("\n Servidor:"+mensaje);
				entroCertificado = true;
				if (mensaje.equals(STATUS+SEPARADOR+REQUEST_AUTH))
				{
					enviarCertificado();
				}
			}
			else
			{
				System.out.println("El certificado recibido No est� correctamente firmado");
			}
		}	
	}

	/**
	 * M�todo en el que se le env�a el certificado digital al servidor junto con la firma digital - Final de la fase 2 del protocolo
	 */
	private void enviarCertificado( ) 
	{	
		out.println(STATUS+SEPARADOR+CLIENT_AUTHENTICATION);
		System.out.println("\n Cliente:"+STATUS+SEPARADOR+CLIENT_AUTHENTICATION);
		try
		{
			KeyPair asimetricas = pair;				
			//------------ Construcci�n del certificado del cliente
			Security.addProvider(proveedor);
			X509V3CertificateGenerator generador = new X509V3CertificateGenerator();
			generador.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
			generador.setIssuerDN(new X500Principal("CN=Test Certificate"));
			generador.setNotBefore(new Date(10,8,20)); // PILAS! El system.currentTimeMillis() es distinto en cada servidor! Mejor usar una fecha segura.
			generador.setNotAfter(new Date(300,8,20));
			generador.setSubjectDN(new X500Principal("CN=Test Certificate"));
			generador.setPublicKey(asimetricas.getPublic());
			generador.setSignatureAlgorithm("SHA256WithRSAEncryption");
			generador.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
			generador.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
			generador.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth));
			generador.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(new GeneralName(GeneralName.rfc822Name, "test@test.test")));		
			cert = generador.generateX509Certificate(asimetricas.getPrivate(), "BC");
			//------------ Validaci�n del certificado
			cert.checkValidity(new Date());
			cert.verify(cert.getPublicKey());
			byte[] certCodificado = cert.getEncoded();// Codifica el certificado a un arreglo de bytes
			DataOutputStream dos = new DataOutputStream(socket.getOutputStream( ));		
			dos.writeInt(certCodificado.length); // Manda un entero indicando el tama�o del certificado
			socket.getOutputStream().write((byte[])certCodificado.clone( ));
			socket.getOutputStream().flush();
			String respu = in.readLine();
			String[] resp = respu.split(SEPARADOR);
			System.out.println("\n Servidor:"+respu);
			if(resp[1].equals(OK))
			{		
				if (opcion==1)
				{
					fase3(resp[2]); // Contin�a a la fase 3 del protocolo
				}
				else if (opcion==2)
				{
					consultarDocumento(resp[2]); // Contin�a a la fase de recuperaci�n de un archivo
				}
				else
				{
					System.out.println("No deber�a entrar");
					//No deber�a suceder
				}
			}
			else
			{
				System.out.println("El certificado enviado estaba con errores. Info:"+respu);
			}		
		}
		catch (SignatureException e)
		{
			e.printStackTrace( );
		} 
		catch (Exception e) 
		{
			e.printStackTrace( );
		}				
	}

	/**
	 * M�todo que solicita recuperar un documento del sistema de pensiones. De cumplir con todos los procedimientos de seguridad y control, 
	 * se guarda el archivo en disco, en la carpeta data
	 * @param resp la cadena de texto enviada por el servidor con la informaci�n de la llave sim�trica
	 */
	private void consultarDocumento(String resp)
	{
		try 
		{
			//--------- Obtenci�n de la llave sim�trica
			String llaveSimetrica = resp;
			byte[] llavec = destransformar(llaveSimetrica.split(SEPARADOR2),"LLAVECIFRADA");		
			Cipher rsaCipher = Cipher.getInstance( ASYMMETRIC );
			rsaCipher.init( Cipher.DECRYPT_MODE, pair.getPrivate( ) );
			byte[] deciphertext = rsaCipher.doFinal(llavec);
			// Acoplar a los bloques necesarios para la creaci�n de la llave sim�trica    
			byte[] temp = new byte[LLAVE_TAM_BITS/8];        
			for (int i = 0; i < temp.length; i++) 
			{
				temp[temp.length-i-1] = deciphertext[deciphertext.length-i-1];
			}
			deciphertext = temp;         
			// Creaci�n de la llave sim�trica con algoritmo AES (Advanced Encription Standard) - Rijndael
			SecretKeySpec skeySpec = new SecretKeySpec( deciphertext, "AES" );			
			Cipher cipher = Cipher.getInstance("AES");
			cipher.init(Cipher.DECRYPT_MODE, skeySpec);
			//----------- Se informa del documento a solicitar
			out.println(RETRIEVE+SEPARADOR+nombreConsulta);
			String respu = in.readLine();
			String[] mnsj = respu.split(SEPARADOR);
			System.out.println("\n Servidor:"+respu);
			if(mnsj[1].equals(OK))
			{	
				//Recibir el documento
				byte[] arreglo = new byte[Integer.parseInt(mnsj[2])];		
				DataInputStream dis = new DataInputStream(socket.getInputStream( ));
				// Obtener el tama�o del archivo
				long length = socket.getInputStream( ).available( );
				if (length>Integer.MAX_VALUE)
				{
					System.out.println("El archivo es demasiado grande");
				}
				else
				{
					// Leer los bytes
					dis.readFully(arreglo); // arreglo contiene el arreglo de bytes enviado por el servidor a trav�s del socket
				}
				//Los bytes enviados se encuentran en base 64, por lo que se deben decodificar.
				byte[] arch = Base64.decode(arreglo);

				//Se descifra el archivo con la llave sim�trica:		
				byte [ ] archDescifrado = cipher.doFinal(arch);
				byte[ ] archFinal = new byte[archDescifrado.length-Integer.parseInt(mnsj[4])];
				for (int i = 0; i < archFinal.length; i++)
				{
					archFinal[i] = archDescifrado[i];
				}
				System.out.println("Tamano del archivo FINAL:"+archFinal.length);

				//----------Verificaci�n del Digest----------//
				byte[] arreglo2 = new byte[Integer.parseInt(mnsj[3])];

				dis.readFully(arreglo2); //arreglo2 contiene el arreglo de bytes del digest enviado por el servidor

				//Se verifica el digest luego de decodificar los bytes de base 64
				boolean digestOk = verificarDigest(Base64.decode(arreglo2), arreglo);		
				if (digestOk)
				{
					//------------ FIRMA--------------//

					//Recibe un arreglo de bytes con la firma
					byte[] bytes1 = new byte[Integer.parseInt(mnsj[5])];
					dis.readFully(bytes1);

					//Verifica la firma digital
					Signature verificador = Signature.getInstance("SHA1withRSA");
					verificador.initVerify(llaveServidor);
					verificador.update(archFinal);
					boolean firmaOk = verificador.verify(bytes1);			
					System.out.println("Estado de la firma digital:"+firmaOk);
					if (firmaOk)
					{
						//Se crea el csv si este no existe
						File temp2 = new File("data/archivo"+nombreConsulta+".csv");
						if (temp2.exists( ))
						{
							temp2.delete( );
						}
						FileOutputStream fos;
						fos = new FileOutputStream(temp2);					
						BufferedOutputStream bos = new BufferedOutputStream(fos);
						//Escribe el archivo en disco
						bos.write(archFinal,0,archFinal.length);
						out.println(RECEIVED+SEPARADOR+OK);
						bos.flush();
						fos.flush();
						bos.close( );
						fos.close();
						System.out.println("El Sistema de Recaudos le desea un feliz d�a");
					}
				}
				else
				{
					System.out.println(RECEIVED+SEPARADOR+DENIED+SEPARADOR+"El digest del documento recibido no concuerda con el digest recibido");
					out.println(RECEIVED+SEPARADOR+DENIED+SEPARADOR+"El digest del documento recibido no concuerda con el digest recibido");
					System.out.println(RECEIVED+SEPARADOR+DENIED);				
				}
			}
			else
			{
				System.out.println("There was a problem while requesting the file. Info:"+respu);
			}	
		}
		catch (NoSuchAlgorithmException e) 
		{
			e.printStackTrace();
		} 
		catch (NoSuchPaddingException e)
		{
			e.printStackTrace();
		} 
		catch (InvalidKeyException e) 
		{
			e.printStackTrace();
		} 
		catch (IllegalBlockSizeException e)
		{
			e.printStackTrace();
		} 
		catch (BadPaddingException e) 
		{
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SignatureException e) {
			e.printStackTrace();
		}
	}

	/**
	 * M�todo encargado de verificar el digest del archivo enviado.
	 * El digest que el cliente debe utilizar es SHA
	 * @param digestRecibido. Bytes con el digest
	 * @param documentoRecibido. Bytes del documento
	 * @return True si el Digest coincide con el documento, false de lo contrario.
	 */
	private boolean verificarDigest(byte[] digestRecibido, byte[] documentoRecibido ) 
	{
		try
		{
			MessageDigest md = MessageDigest.getInstance( "SHA" );
			md.update( documentoRecibido,0,documentoRecibido.length );
			byte[] resultado = md.digest( );
			return MessageDigest.isEqual(digestRecibido, resultado);
		}
		catch( Exception e )
		{	            
			e.printStackTrace();
			out.println(e.getMessage( ));
		}
		return false;
	}

	/**
	 * M�todo que ejecuta la fase 3 del protocolo; la recepci�n de la llave sim�trica y el posterior env�o del archivo de empleados
	 * utilizando esa llave sim�trica.
	 * @param resp una cadena de caracteres conteniendo la llave sim�trica
	 */
	private void fase3(String resp) 
	{
		try 
		{
			//--------- Obtenci�n de la llave sim�trica
			String llaveSimetrica = resp;
			byte[] llavec = destransformar(llaveSimetrica.split(SEPARADOR2),"LLAVECIFRADA");		
			Cipher rsaCipher = Cipher.getInstance( ASYMMETRIC );
			rsaCipher.init( Cipher.DECRYPT_MODE, pair.getPrivate( ) );
			byte[] deciphertext = rsaCipher.doFinal(llavec);
			// Acoplar a los bloques necesarios para la creaci�n de la llave sim�trica    
			byte[] temp = new byte[LLAVE_TAM_BITS/8];        
			for (int i = 0; i < temp.length; i++) 
			{
				temp[temp.length-i-1] = deciphertext[deciphertext.length-i-1];
			}
			deciphertext = temp;         
			// Creaci�n de la llave sim�trica con algoritmo AES (Advanced Encription Standard) - Rijndael
			SecretKeySpec skeySpec = new SecretKeySpec( deciphertext, "AES" );			
			Cipher cipher = Cipher.getInstance("AES");
			cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
			//----------- Preparaci�n del documento y digest para env�o
			byte[] documento = CrearDocumento(); // Contiene el documento como un arreglo de bytes		
			int mod = documento.length%cipher.getBlockSize( ); // Representa la longitud del archivo m�dulo el tama�o de bloque de cifrado
			int residuo = (mod==0)?0:cipher.getBlockSize( ) - mod; // El residuo, que se usar� para crear un arreglo de bytes cuya longitud es m�ltiplo del tama�o de bloque de cifrado
			byte[] paddedDoc = new byte[documento.length+residuo];
			// Pasar los datos del documento al nuevo arreglo de bytes para permitir el correcto padding en el servidor
			for (int i = 0; i < documento.length; i++)
			{
				paddedDoc[i] = documento[i];
			}

			// Firmar y enviar la firma como un arreglo de bytes:
			Signature firmar = Signature.getInstance("SHA1withRSA"); // La firma utilizando el algoritmo SHA1 junto con el algoritmo de encripci�n asim�trico RSA
			firmar.initSign(pair.getPrivate( ));
			firmar.update(documento);
			byte[] firma = firmar.sign( );
			byte[] res = cipher.doFinal(paddedDoc); // Se cifra con la llave sim�trica el nuevo documento con longitud m�ltiplo de el bloque de cifrado
			byte[] respuesta = Base64.encode(res); // Se codifica en base 64 para evitar conflictos con caracteres especiales durante el env�o
			byte[] digest = obtenerDigest(respuesta);
			byte[] elDigest = Base64.encode(digest); // Igualmente se codifica en base 64
			//----------- Enviar mensaje de inicio de env�o, archivo cifrado y digest
			out.println(UPDATE+SEPARADOR+respuesta.length+SEPARADOR+elDigest.length+SEPARADOR+residuo+SEPARADOR+firma.length);	
			System.out.println("\n Cliente:"+UPDATE+SEPARADOR+respuesta.length+SEPARADOR+elDigest.length+SEPARADOR+residuo+SEPARADOR+firma.length);
			
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream( ));			
			
			// Enviar el archivo cifrado 
			dos.write(respuesta);
			socket.getOutputStream( ).flush( );
			// Env�a digest como stream de bytes	
			dos.write(elDigest);
			socket.getOutputStream( ).flush( );
			// Env�a la firma del documento
			dos.write(firma);	
			socket.getOutputStream( ).flush( );

			//----------- Respuesta del servidor
			String confirm= in.readLine();			
			System.out.println("\n Servidor:" + confirm);
			if(confirm.equals(RECEIVED+SEPARADOR+OK))
			{			
				out.println("FIN");
				System.out.println("El Sistema de Recaudos le desea un feliz d�a");
			}
			else
			{
				System.out.println("Error durante el env�o del archivo; el servidor dice: "+  confirm);
			}		
		} 
		catch (NoSuchAlgorithmException e) 
		{
			e.printStackTrace();
		} 
		catch (NoSuchPaddingException e)
		{
			e.printStackTrace();
		} 
		catch (InvalidKeyException e) 
		{
			e.printStackTrace();
		} 
		catch (IllegalBlockSizeException e)
		{
			e.printStackTrace();
		} 
		catch (BadPaddingException e) 
		{
			e.printStackTrace();
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		} catch (SignatureException e) {
			e.printStackTrace();
		} 				
	}

	/**
	 * M�todo que recoge el documento a partir de un archivo en disco y lo retorna
	 * @return el archivo a enviar al servidor como un arreglo de bytes
	 */
	private byte[] CrearDocumento()
	{		
		try
		{		
			byte[] archivo = new byte[(int) generator.darTamanoArchivo( )];
			File archivoEnDisco = new File("data/prueba.csv");		
			FileInputStream fileInputStream = new FileInputStream(archivoEnDisco);
			fileInputStream.read(archivo);
			fileInputStream.close( );
			return archivo;	
		} 
		catch (FileNotFoundException e) 
		{
			System.out.println("File Not Found.");
			e.printStackTrace();
		} 
		catch (IOException e)
		{		
			e.printStackTrace();
		}	
		return null;
	}

	/**
	 * M�todo que genera el digest SHA a partir de un arreglo de bytes
	 * @param documento el documento como arreglo de bytes
	 * @return el digest como un arreglo de bytes
	 */
	private byte[] obtenerDigest(byte[] documento) 
	{
		try
		{
			MessageDigest md = MessageDigest.getInstance( "SHA" ); // Crea una instancia del digest con algoritmo SHA
			md.update( documento,0,documento.length ); // Actualiza al digest el documento que se enviar� al servidor
			byte[] resultado = md.digest( ); // Generaci�n del digest
			return resultado;
		}
		catch( Exception e )
		{
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Inicio de la fase 2 del protocolo. Se verifica el digest del servidor, y que este est� correctamente firmado digitalmente
	 * @return true si el certificado del servidor es v�lido, false de lo contrario.
	 */
	private boolean procesarCertificadoServidor( )
	{	
		try 
		{
			DataInputStream dis = new DataInputStream(socket.getInputStream( ));
			//Recibe un arreglo de bytes con la firma:

			//Recibe el certificado original como un arreglo de bytes
			byte[] certServidor = new byte[dis.readInt( )];
			socket.getInputStream( ).read(certServidor, 0, certServidor.length );

			//Verifica el certificado enviado por el servidor.
			X509Certificate cert = X509Certificate.getInstance(certServidor);
			cert.checkValidity();	// Verifica que no haya conflicto de fechas y otros errores de construcci�n del certificado	         
			return true;

		} 
		catch (IOException e)
		{			
			e.printStackTrace();
		} 
		catch (CertificateException e)
		{						
			e.printStackTrace();
		} 

		return false;
	}

	/**
	 * Main de la aplicaci�n, se crea una instancia de la clase Recaudadora e inicia el protocolo
	 * @param args args
	 */
	public static void main( String args[] )
	{
		for (int i = 0; i < QUERY_QUANTITY; i++) {
			Recaudadora app= new Recaudadora();	
			app.start();
		}
	}

	/**
	 * Algoritmo de encapsulamiento a enteros. Convierte los bytes de un String a su representacion como enteros.
	 * @param b Los bytes a representar como enteros.
	 * @return EL string construido con la representacion de bytes como enteros.
	 */
	public String transformar( byte[] b )
	{
		String s=null;
		try
		{
			s = new String("".getBytes(),COD);
		}
		catch (UnsupportedEncodingException e) 
		{
			e.printStackTrace();
		}
		for (int i = 0; i < b.length; i++) 
		{
			if( i == b.length-1 )
				s += Byte.toString(b[i]);
			else
				s += Byte.toString(b[i])+SEPARADOR2;
		}
		return s;
	}

	/**
	 * Algoritmo que transforma los enteros en los bytes correspondientes.
	 * @param s El string con los enteros a transformar.
	 * @param ss La cadena de peticion.
	 * @return Los bytes en su representacion real.
	 */
	public byte[] destransformar( String[] s, String ss )
	{
		byte[] b = new byte[s.length];		
		for (int i = 0; i < b.length; i++) 
		{
			try
			{
				b[i] = Byte.parseByte(s[i],10);
			} 
			catch (Exception e) 
			{
				System.out.println(ss+SEPARADOR+e.getMessage());
			} 
		}	
		return b;
	}
}
